﻿using System.Drawing;
using FastColoredTextBoxNS;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Text.RegularExpressions;


namespace MPE
{
    internal static class ErrorChecker
    {
        public static int errorCount = 0;
        static List<Range> JSi = new List<Range>();//Положение блока [JS]
        static readonly List<Range> ifs = new List<Range>();//Положение блока [IF]
        static Properties.Settings param = Properties.Settings.Default;
        static FastColoredTextBox field { get => DataHelper.field; }
        public static void needCheckErrors(ref Label errorCount)
        {
            field.Hints.Clear(); ifs.Clear(); break_points.Clear(); go_to_break_points.Clear(); JSi.Clear();
            int ifs_Count = 0, elses_Count = 0;
            ErrorChecker.errorCount = 0;
            bool endOfJS = true, multilineComment = false;
            for (int i = 0; i <= field.Range.ToLine; i++)
            {
                Range currLine = field.GetLine(i);//Проверяемая строка
                string clearedText = currLine.Text.Trim();
                if (DataHelper.brakets_1.Match(clearedText).Success)
                {
                    if (multilineComment && param.errorSettings[19] == '1')
                    { markLine(currLine, "При многострочном комментировании нельзя добавлять внутренние комментарии!"); }
                    continue;//Однострочный комментарий
                }
                else if (DataHelper.brakets_begin_multi_comment.Match(clearedText).Success)
                {
                    Match match = DataHelper.brakets_from_begin.Match(clearedText);
                    if (match.Success)
                        if (clearedText.Replace(match.Value, "").Length > 0)
                        {
                            if (param.errorSettings[20] == '1')
                                markLine(currLine, "Функция после комментария будет выполнена!");
                            continue;
                        }

                    if (multilineComment && param.errorSettings[19] == '1')
                    { markLine(currLine, "При многострочном комментировании нельзя добавлять внутренние комментарии!"); continue; }
                    else { multilineComment = true; continue; }
                }
                else if (DataHelper.brakets_end_all_multi.Match(clearedText).Success) { multilineComment = false; continue; }
                if (multilineComment) continue;

                DataHelper.getClearedString(ref clearedText);

                if (JS.Match(clearedText).Success) { endOfJS = false; JSi.Add(currLine); }
                else if (EVALUATE_JS_inline.Match(clearedText).Success) { endOfJS = true; if (JSi.Count > 0) JSi.RemoveAt(JSi.Count - 1); continue; }
                else if (_JS.Match(clearedText).Success) { endOfJS = true; if (JSi.Count > 0) JSi.RemoveAt(JSi.Count - 1); }
                else if (param.errorSettings[16] == '1' && EVALUATE_JS.Match(clearedText).Success) { markLine(currLine, "Неправильный формат [EVALUATE_JS]! Функция должна принимать один параметр"); endOfJS = false; continue; }

                clearedText = clearedText.Replace("\\]", "@").Replace("\\[", "@");

                string[] split = clearedText.Split(new string[] { "]" }, System.StringSplitOptions.None);
                string valid;
                if (param.errorSettings[7] == '1' && badWord.Match(clearedText).Success)
                { markLine(currLine, "Лишние символы перед ключевым словом!"); continue; }
                else if (param.errorSettings[4] == '1' && clearedText.Length > 0 && endOfJS && !(validFunk.TryGetValue(split[0].Contains("[") ? split[0] + "]" : "[" + split[0] + "]", out valid) || validFunk.TryGetValue(split[0], out valid)))
                {
                    markLine(currLine, "Неизвестная функция!"); continue;
                }

                int openedBrackets = 0, closedBrakets = 0;
                for (int d = 0; d < clearedText.Length; d++)
                    if (clearedText[d] == '[') openedBrackets++;
                    else if (clearedText[d] == ']') closedBrakets++;

                if (param.errorSettings[17] == '1' && openedBrackets != closedBrakets)
                    markLine(currLine, "Число скобок не совпадает, возможно пропущено экранирование(\\)");

                if (split.Length >= 2)
                {
                    if (validFunk.TryGetValue(split[0].Contains("[") ? split[0] + "]" : "[" + split[0] + "]", out valid))
                    {
                        int counter = 0;//Число параметров
                        for (int c = 1; c < split.Length; c++) if (split[c].Length > 0) counter++;
                        if (comment.Match(split[split.Length - 1]).Success) counter--;
                        if (param.errorSettings[13] == '1' && valid == "00" && split.Length > 2) { markLine(currLine, "Функция не должна принимать параметры!"); }

                        else if (param.errorSettings[5] == '1' && counter < System.Convert.ToInt32(valid[0]) - 48)//min brackets
                            markLine(currLine, "У функции слишком мало параметров!");

                        else if (param.errorSettings[6] == '1' && endOfJS && counter > System.Convert.ToInt32(valid[1]) - 48)//max brackets
                            markLine(currLine, "У функции слишком много параметров!");
                    }
                }

                if (IF.Match(clearedText).Success)
                {
                    ifs.Add(currLine); ifs_Count++;
                    if (param.errorSettings[0] == '1' && (clearedText == "[IF]" || clearedText == "[IF][]" || clearedText == "[IF][][]" || clearedText == "[IF][][][]"))
                        markLine(currLine, "Условие [IF] не имеет параметров!");
                }
                else if (ELSE.Match(clearedText).Success)
                {
                    if (param.errorSettings[2] == '1' && ++elses_Count > ifs_Count)
                        markLine(currLine, "Блоков [ELSE] больше чем блоков [IF]");
                }
                else if (ENDIF.Match(clearedText).Success)
                {
                    if (ifs.Count > 0) ifs.Remove(ifs[ifs.Count - 1]);
                    else if (param.errorSettings[3] == '1') markLine(currLine, "Лишний [ENDIF]");
                }
                else
                {
                    split = currLine.Text.Split(new string[] { "[" }, System.StringSplitOptions.None);
                    if (DataHelper.BREAK_POINT.Match(clearedText).Success)
                    {
                        if (split.Length >= 3)
                        {
                            if (split[2].Contains("]")) split[2] = split[2].Remove(split[2].IndexOf(']'), split[2].Length - split[2].IndexOf(']'));
                            if (break_points.ContainsKey(split[2]))
                            {
                                break_points.TryGetValue(split[2], out int line);
                                if (param.errorSettings[8] == '1')
                                    markLine(currLine, $"Метка возврата с таким именем уже объявлена на {line + 1} строке");
                            }
                            else break_points.Add(split[2], currLine.ToLine);
                        }
                        else if (param.errorSettings[14] == '1') markLine(currLine, $"Неправильный формат блока [BREAK_POINT]");
                    }
                    else if (DataHelper.GO_TO_BREAK_POINT.Match(currLine.Text).Success)
                    {
                        if (split.Length >= 3)
                        {
                            if (split[2].Contains("]")) split[2] = split[2].Remove(split[2].IndexOf(']'), split[2].Length - split[2].IndexOf(']'));
                            if (go_to_break_points.ContainsKey(split[2]))
                                go_to_break_points[split[2]].Add(currLine.ToLine);

                            else go_to_break_points.Add(split[2], new List<int>() { currLine.ToLine });
                        }
                        else if (param.errorSettings[15] == '1') markLine(currLine, $"Неправильный формат блока [GO_TO_BREAK_POINT]");
                    }
                    else if (param.errorSettings[12] == '1' && emptyFunctionParams.Match(clearedText).Success && !ignore_params.Match(clearedText).Success) markLine(currLine, "Функция без параметров!");
                }
            }

            if (param.errorSettings[1] == '1' && ifs.Count > 0) foreach (Range item in ifs)
                    markLine(item, "Условие [IF] должно всегда закрываться блоком [ENDIF]");

            if (param.errorSettings[9] == '1' && break_points.Count > 0)
            {
                Dictionary<string, int>.Enumerator en = break_points.GetEnumerator();

                while (en.MoveNext())
                    if (!go_to_break_points.TryGetValue(en.Current.Key, out List<int> l))
                    {
                        break_points.TryGetValue(en.Current.Key, out int line);
                        markLine(field.GetLine(line), "Метка возврата не имеет закрывающегося блока [GO_TO_BREAK_POINT]");
                    }
            }

            if (go_to_break_points.Count > 0)
            {
                Dictionary<string, List<int>>.Enumerator en = go_to_break_points.GetEnumerator();

                while (en.MoveNext())
                    if (!break_points.TryGetValue(en.Current.Key, out int line) && param.errorSettings[10] == '1')
                        foreach (int item in go_to_break_points[en.Current.Key])
                            markLine(field.GetLine(item), "Точка возврата не имеет объявления через [BREAK_POINT]");

                    else
                        if (param.errorSettings[11] == '1')
                        foreach (int item in go_to_break_points[en.Current.Key])
                            if (line > item) markLine(field.GetLine(item), "Точка возврата объявлена раньше метки [BREAK_POINT]");
            }

            if (param.errorSettings[18] == '1' && JSi.Count > 0) foreach (Range r in JSi)
                    markLine(r, "[JS] должен быть закрыт блоком [/JS]");

            if (ErrorChecker.errorCount > 0)
            {
                errorCount.Content = $"Обнаружено {ErrorChecker.errorCount} ошибки!";
                errorCount.Visibility = System.Windows.Visibility.Visible;
            }
            else errorCount.Visibility = System.Windows.Visibility.Hidden;
        }
        //readonly static Color errors = Color.Red;//Цвет ошибок    
        public static string getCommandBracketsRule(string command) => validFunk.TryGetValue(command, out string output) ? output : "";
        readonly static Dictionary<string, string> validFunk = new Dictionary<string, string>();
        public static Dictionary<string, int> break_points = new Dictionary<string, int>();
        public static Dictionary<string, List<int>> go_to_break_points = new Dictionary<string, List<int>>();
        static void markLine(Range currLine, string errorMsg)
        {// Установить ошибку для строки
            errorCount++;
            Hint hint = new Hint(currLine, errorMsg, true, true) { Height = 30, BackColor = Color.Red };

            hint.Click += copyHintMsg;
            field.Hints.Add(hint);
        }
        static void copyHintMsg(object sender, System.EventArgs e)
        {// Копировать текст ошибки при клике
            if ((System.Windows.Input.Keyboard.Modifiers & System.Windows.Input.ModifierKeys.Control) == System.Windows.Input.ModifierKeys.Control
                && param.whenCopyErrorMsg == 0)
                copyMsg(sender);

            else if (param.whenCopyErrorMsg == 1)
                copyMsg(sender);
        }
        static void copyMsg(object sender)
        {
            if (System.Windows.MessageBox.Show("Копировать текст ошибки в буфер обмена?", "Копирование ошибки", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes)
                System.Windows.Clipboard.SetText((sender as UnfocusablePanel).Text);
        }
        readonly static Regex badWord = new Regex(@"^\s*[\w\d]+\s*\[\w+]"),
                      emptyFunctionParams = new Regex(@"^\[\w*\](\[\]){1,3}"),
                      IF = new Regex(@"^\s*\[IF\]"),
                      ELSE = new Regex(@"^\s*\[ELSE\]"),
                      ENDIF = new Regex(@"^\s*\[ENDIF\]"),
                      comment = new Regex(@"(^\s*[{]+)|(\{+.+\}+\s*$)"),
                      EVALUATE_JS = new Regex(@"\s*\[(EVALUATE_JS){1}\]\s*$"),
                      EVALUATE_JS_inline = new Regex(@"\s*\[(EVALUATE_JS){1}\]\s*\[.*\]"),
                      ignore_params = new Regex(@"^\s*\[(SET_REFERER|LOG_DISPLAY)\]");
        public readonly static Regex JS = new Regex(@"\[(JS){1}\]\s*$"),
                                    _JS = new Regex(@"\s*\[(/JS)\]\s*$");
    }
}
